package com.navidove.commons.dao.impl;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate4.support.HibernateDaoSupport;

import com.navidove.commons.dao.Dao;
import com.navidove.commons.vo.Paging;


public class DaoImpl<T extends Serializable, P extends Serializable> extends HibernateDaoSupport implements Dao<T, P> {

    protected final Logger logger = LoggerFactory.getLogger(this.getClass().getName());
    
    @Autowired
    public void init(SessionFactory sessionFactory) {
        setSessionFactory(sessionFactory);
    }

    @SuppressWarnings("unchecked")
    public Class<T> getClazz() {
    	return (Class<T>)((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }
    
    @SuppressWarnings("unchecked")
    public Class<P> getPrimaryKeyClass() {
        return (Class<P>)((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[1];
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<T> getAll() {
        DetachedCriteria criteria = DetachedCriteria.forClass(getClazz());
        return (List<T>) getHibernateTemplate().findByCriteria(criteria);
    }

    @Override
    public T get(P pk) {
        return getHibernateTemplate().get(getClazz(), pk);
    }

    @SuppressWarnings("unchecked")
    public T get(Criterion... conditions) {
        DetachedCriteria criteria = DetachedCriteria.forClass(getClazz());
        for (Criterion c : conditions) {
            criteria.add(c);
        }
        return (T) this.getByCriteria(criteria);
    }

    public T get(String property, Object value) {
        return this.get(Restrictions.eq(property, value).ignoreCase());
    }

    @Override
    public void save(T entity) {
        getHibernateTemplate().saveOrUpdate(entity);
    }

    public void saveAll(Collection<T> entitys) {
    	for (T entity : entitys) {
    		getHibernateTemplate().saveOrUpdate(entity);
    	}
    }

    public void delete(T entity) {
        getHibernateTemplate().delete(entity);
    }

    public void deleteAll(Collection<T> entitys) {
        getHibernateTemplate().deleteAll(entitys);
    }

    public List<?> getListByCriteria(DetachedCriteria criteria) {
        return getListByCriteria(criteria, null);
    }

    public List<?> getListByCriteria(DetachedCriteria detachedCriteria,
            Paging paging) {

        if (paging != null && paging.getObjectsPerPage() > 0) {
            detachedCriteria.setProjection(Projections.rowCount());
            Number count = (Number) getByCriteria(detachedCriteria);            
            paging.setFullSize(count.intValue());            
            detachedCriteria.setProjection(null);
            detachedCriteria.setResultTransformer(Criteria.ROOT_ENTITY);
        }
        
        if (paging != null && MapUtils.isNotEmpty(paging.getOrder())) {
            Iterator<Entry<String, String>> it = paging.getOrder().entrySet().iterator();
            while(it.hasNext()) {
            	Entry<String, String> entry = it.next();
            	String field = entry.getKey();
            	String dir = entry.getValue();
            	
            	if (dir.toUpperCase().equals("ASC")) {
            		detachedCriteria.addOrder(Order.asc(field));
            	} else if (dir.toUpperCase().equals("DESC")) {
            		detachedCriteria.addOrder(Order.desc(field));
            	}
            }
        }
        
        if (paging != null && paging.getObjectsPerPage() > 0) {
            Criteria criteria = detachedCriteria.getExecutableCriteria(getSession());
            criteria.setFirstResult(paging.getFirstResult());
            criteria.setMaxResults(paging.getObjectsPerPage());
            return criteria.list();
        }

        return getHibernateTemplate().findByCriteria(detachedCriteria);
    }

    @SuppressWarnings("unchecked")
    public List<T> getList(Criterion... conditions) {
        DetachedCriteria criteria = DetachedCriteria.forClass(getClazz());
        for (Criterion c : conditions) {
            criteria.add(c);
        }
        return (List<T>)this.getListByCriteria(criteria);
    }

    public Object getByCriteria(DetachedCriteria criteria) {
        List<?> list = getListByCriteria(criteria);
        if (CollectionUtils.isNotEmpty(list)) {
            return list.get(0);
        }
        return null;
    }

    public Object getByQueryString(CharSequence hql, Object... values) {
    	List<Object> list = getListByQueryString(null, hql, values);
    	if (list.size()>0) return list.get(0);
    	return null;
    }
    
	public List<?> getListByQueryString(CharSequence hql, Object... values) {
	    return getHibernateTemplate().find(hql.toString(), values);
	}
	
    @SuppressWarnings("unchecked")
	public List<Object> getListByQueryString(Paging paging,
    		CharSequence hql, Object... values) {
    	
    	Query query = getSession().createQuery(hql.toString());
    	if (values!=null&&values.length>0){
    		for (int i=0;i<values.length;i++){
    			query.setParameter(i, values[i]);
    		}
    	}
    	if (paging!=null && paging.getObjectsPerPage() > 0){
    		query.setFirstResult(paging.getFirstResult());
    		query.setMaxResults(paging.getObjectsPerPage());
    	}
    	
    	return (List<Object>)query.list();
    }
    
	@SuppressWarnings({ "rawtypes" })
	public List getListBySql(String sql) {

		Session session = this.getSession();
		SQLQuery query = session.createSQLQuery(sql);
		List list = query.list();

		return list;
	}

	@SuppressWarnings("rawtypes")
	public List getListBySql(String sql, Object... values) {
		return getListBySql(null, sql, values);

	}

	@SuppressWarnings("rawtypes")
	public List getListBySql(Paging paging, String sql,
			Object... values) {
		
		SQLQuery query = getSession().createSQLQuery(sql);
		if (paging!=null && paging.getObjectsPerPage() > 0){
			query.setFirstResult(paging.getFirstResult());
			query.setMaxResults(paging.getObjectsPerPage());
		}
        if (values != null && values.length > 0) {
            for (int i = 0; i < values.length; i++) {
                query.setParameter(i, values[i]);
            }
        }
		return query.list();
	}
	
	public int executeByQueryString(String queryString, Object... values) {
	    return getHibernateTemplate().bulkUpdate(queryString, values);
	}	

	public int executeBySql(String sql, Object... values) {
		SQLQuery query = getSession().createSQLQuery(sql);
        if (values != null && values.length > 0) {
            for (int i = 0; i < values.length; i++) {
                query.setParameter(i, values[i]);
            }
        }
		return query.executeUpdate();
	}

	public void flush() {
	    getHibernateTemplate().flush();
	}

	public void clear() {
	    getHibernateTemplate().clear();
	}	
	
	protected Session getSession() {
		return getHibernateTemplate().getSessionFactory().getCurrentSession();
	}
}
